package com.xw.unification.utils;

import com.xw.unification.enums.DigestType;
import com.xw.unification.helper.StringHelper;

import javax.crypto.Mac;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;

public abstract class SignatureUtils {

    private static final int MAX_BYTE_ARRAY_LENGTH = 512;

    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    public static String digest(DigestType digestType, CharSequence charSequence, String secret, String charsetName) {
        if (charSequence == null) {
            return null;
        }
        try {
            return digest(digestType, new ByteArrayInputStream(charSequence.toString().getBytes(charsetName)), secret, charsetName);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public static String digest(DigestType digestType, InputStream inputStream, String secret, String charsetName) {
        if (inputStream == null || digestType == null) {
            return null;
        }
        if (digestType == DigestType.MD5) {
            return digestByMessageDigest(digestType, inputStream, secret, charsetName);
        }
        if (digestType == DigestType.SHA_1 ||digestType == DigestType.SHA_256) {

            return StringHelper.hasText(secret) ?
                    digestByMac(digestType, inputStream, secret, charsetName)
                    : digestByMessageDigest(digestType, inputStream, secret, charsetName)
                    ;
        }
        return null;
    }

    public static String digest(DigestType digestType, CharSequence charSequence, String secret) {
        return digest(digestType, charSequence, secret, DEFAULT_CHARSET.name());
    }


    public static String digest(DigestType digestType, InputStream inputStream, String secret) {
        return digest(digestType, inputStream, secret, DEFAULT_CHARSET.name());
    }

    public static String digest(DigestType digestType, CharSequence charSequence) {
        return digest(digestType, charSequence, null, DEFAULT_CHARSET.name());
    }


    public static String digest(DigestType digestType, InputStream inputStream) {
        return digest(digestType, inputStream, null, DEFAULT_CHARSET.name());
    }

    private static MessageDigest getDigest(DigestType digestType) {
        if (digestType == null) {
            throw new IllegalArgumentException("Digest type can not be null.");
        }
        MessageDigest digest = null;
        try {
            digest = MessageDigest.getInstance(digestType.getAlgorithmName());
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("No digest of algorithm with name: " + digestType.getAlgorithmName());
        }
        if (digest == null) {
            throw new IllegalArgumentException("No digest of algorithm with name: " + digestType.getAlgorithmName());
        }
        return digest;
    }


    private static String digestByMessageDigest(DigestType digestType, InputStream inputStream, String secret, String charsetName) {
        MessageDigest digest = getDigest(digestType);
        synchronized (digestType) {
            int length = -1;
            byte[] byteArray = new byte[MAX_BYTE_ARRAY_LENGTH];
            try {
                while ((length = inputStream.read(byteArray)) > 0) {
                    digest.update(byteArray, 0, length);
                }
                return toHexString(digest.digest());
            } catch (IOException e) {
                throw new RuntimeException("Digest Content update exception.", e);
            }
        }
    }

    private static String digestByMac(DigestType digestType, InputStream inputStream, String secret, String charsetName) {
        Charset charset = getCharsetByName(charsetName);
        String macAlgName = digestType.getMacAlgName();
        if (!StringHelper.hasText(macAlgName)) {
            throw new RuntimeException("Mac Algorithm name can not be null.");
        }
        SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(charset), macAlgName);
        int length = -1;
        byte[] byteArray = new byte[MAX_BYTE_ARRAY_LENGTH];
        try {
            Mac mac = Mac.getInstance(macAlgName);
            mac.init(keySpec);
            while ((length = inputStream.read(byteArray)) > 0) {
                mac.update(byteArray, 0, length);
            }
            return toHexString(mac.doFinal());
        } catch (Exception e) {
            return null;
        }
    }

    private static Charset getCharsetByName(String charsetName) {
        Charset charset = null;
        try {
            return Charset.forName(charsetName);
        } catch (Exception e) {
            throw new IllegalCharsetNameException(charsetName);
        }
    }


    private static String toHexString(byte[] bytes) {
        if (bytes == null) {
            return null;
        }
        StringBuilder builder = new StringBuilder();
        String tmp;
        for (byte b : bytes) {
            tmp = Integer.toHexString(b & 0xFF).toLowerCase(Locale.ROOT);
            if (tmp.length() < 2) {
                builder.append('0');
            }
            builder.append(tmp);
        }
        return builder.toString();
    }
}
